package com.tianji.learning.service.impl;

import com.tianji.common.autoconfigure.mq.RabbitMqHelper;
import com.tianji.common.constants.MqConstants;
import com.tianji.common.exceptions.BizIllegalException;
import com.tianji.common.utils.CollUtils;
import com.tianji.common.utils.UserContext;
import com.tianji.learning.constants.RedisConstants;
import com.tianji.learning.domain.vo.SignResultVO;
import com.tianji.learning.mq.message.SignInMessage;
import com.tianji.learning.service.ISignRecordService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.connection.BitFieldSubCommands;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.List;

/**
 * @author Yandel
 * @date 2025/5/26 下午8:55
 * @description:
 */
@Service
@Slf4j
@RequiredArgsConstructor
public class ISignRecordServiceImpl implements ISignRecordService {

    private final StringRedisTemplate redisTemplate;
    private final RabbitMqHelper mqHelper;

    @Override
    public SignResultVO addSignRecords() {
        //获取当前登录用户端id
        Long userId = UserContext.getUser();
        // 拼接key
        LocalDate now = LocalDate.now();// 当前时间的年月
        log.info("当期那时间：{}",  now);
        String format = now.format(DateTimeFormatter.ofPattern(":yyyyMM"));
        String key = RedisConstants.SIGN_RECORD_KEY_PREFIX + userId.toString() + format;
        log.info("key的值为是:{}", key);
        // 利用bitset命令 将签到记录保存到redis的bitmap结构中 需要校验是否已签到
        int offset = now.getDayOfMonth() - 1;//偏移量
        Boolean setBit = redisTemplate.opsForValue().setBit(key, offset, true);
        // 返回值是该offset上原来的值 原来为0是false 原来为1是true
        if (setBit) {
            // 说明当前已经签过到了
            throw new BizIllegalException("不能重复签到");
        }
        // 计算连续签到天数
        int days = countSignDays(key, now.getDayOfMonth());
        // 计算连续签到 奖励积分
        int rewardPoints = 0;
        switch (days) {
            case 7:
                rewardPoints = 10;
                break;
            case 14:
                rewardPoints = 20;
                break;
            case 28:
                rewardPoints = 40;
                break;
        }
        //发送消息
        mqHelper.send(MqConstants.Exchange.LEARNING_EXCHANGE,
                MqConstants.Key.SIGN_IN,
                SignInMessage.of(userId,rewardPoints + 1));//设置发送的消息值——SignInMessage

        // 封装vo返回
        SignResultVO vo = new SignResultVO();
        vo.setSignDays(days);
        vo.setRewardPoints(rewardPoints);
        return vo;

    }

    /**
     * 计算连续签到的天数
     *
     * @param key        缓存中的key
     * @param dayOfMonth 本月第一天到今天的天数
     * @return
     */
    private int countSignDays(String key, int dayOfMonth) {
        // 求本月第一天到今天签到的天数 bitFiled 得到的是十进制
        // bitField key get u天数 0
        List<Long> bitField = redisTemplate.opsForValue()
                .bitField(key, BitFieldSubCommands
                        .create().get(BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth)).valueAt(0));
        if (CollUtils.isEmpty(bitField)) {
            return 0;
        }
        Long num = bitField.get(0);// 本月第一天到今天的签到天数 拿到的十进制
        log.debug("num {}", num);
        // num转二进制 从后往前推共有多少个一
        int counter = 0;// 计数器
        while ((num & 1) == 1) {
            counter++;
            num = num >>> 1;// 右移一位
        }
        return counter;
    }


    @Override
    public Byte[] querySignRecords() {
        //获取当前登录用户
        Long userId = UserContext.getUser();
        //拼接key  sign:uid:userId:202305
        LocalDate now = LocalDate.now();
        String format = now.format(DateTimeFormatter.ofPattern(":yyyyMM"));// 当前年月  格式->    :202305
        String key = RedisConstants.SIGN_RECORD_KEY_PREFIX + userId.toString() + format;

        // 取本月第一天到今天所有的签到记录
        int dayOfMonth = now.getDayOfMonth();
        // bitfield key get u天数 0
        List<Long> bitField = redisTemplate.opsForValue()
                //使用Redis的BITFIELD命令，读取指定key对应的位图字段
                .bitField(key,
                        //构造一个获取无符号整数的子命令，长度为当月天数。
                        BitFieldSubCommands.create().get(BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth))
                                //表示从字节偏移量0开始读取
                                .valueAt(0));
        if (CollUtils.isEmpty(bitField)){
            return new Byte[0];
        }
        Long num = bitField.get(0);
        int offset = now.getDayOfMonth() - 1;

        // 利用& 封装结构
        Byte[] arr = new Byte[dayOfMonth];
        while (offset >= 0){
            arr[offset] = (byte) (num & 1);// 计算最后一天是否签到 赋值结果
            offset--;
            // 把数字右移一位，抛弃最后一个bit位，继续下一个bit位
            num >>>= 1;
        }
        return arr;
    }
}
